package fun.yao.common.db;

import fun.yao.common.Constant;
import fun.yao.common.exception.GessErrCode;
import fun.yao.common.exception.GessException;
import fun.yao.common.uitls.LogUtil;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * 连接池
 * @author csl
 */
public class ConnPool extends Thread
{
    //连接池的数据库类型：当前实时数据库
    public static final int CONN_POOL_TYPE_CURR = 1;
    //连接池的数据库类型：历史数据库
    public static final int CONN_POOL_TYPE_HIS = 2;

    //是否运行
    protected boolean bIsRun = true;

    //数据库驱动
    protected String mDbDriver = "" ;
    //数据库连接串
    protected String mDbUrl = "" ;
    //数据库用户名
    protected String mDbUid = "" ;
    //数据库密码
    protected String mDbPwd = "" ;

    //数据库探测SQL
    protected String mTestSql = "";
    //数据库SQL探测的间隔时间（秒）
    protected int    mSqlTestInvTime = 10 ;

    //连接池忙时，获取连接时默认的最大等待时间（秒）
    protected int    mGetWaitTime = 120;

    //连接池容量
    protected int mPoolSize = 0 ;

    //连接建立后的超时时间（秒）
    protected int mConnOverTime = 4*60*60 ;

    //空闲等待的连接队列
    protected ConcurrentLinkedQueue<GessConnection> idleQueue = new ConcurrentLinkedQueue<GessConnection>();
    //正在使用中的连接数量
    protected int mUseCount = 0 ;

    //上次打印连接池情况的时间
    protected long mLastPrintTime = System.currentTimeMillis() ;

    //是否为当前实时数据库的连接
    protected int mDbType =  CONN_POOL_TYPE_CURR ;

    //构造函数
    public ConnPool()
    {
    }

    //设置连接池相关的配置信息
    public void setJdbcInfo(String driver,String url,String uid,String pwd)
    {
        this.mDbDriver = driver;
        this.mDbUrl = url;
        this.mDbUid = uid;
        this.mDbPwd = pwd;
    }

    //设置连接池的大小
    public void setPoolSize(int size)
    {
        this.mPoolSize = size ;
    }

    //设置连接测试的SQL
    public void setTestSql(String sql)
    {
        this.mTestSql  = sql ;
    }

    //设置SQL探测的时间间隔
    public void setSqlTestInvTime(int time)
    {
        this.mSqlTestInvTime = time;
    }

    //设置连接建立后的超时时间（秒）
    public void setConnOverTime(int time)
    {
        this.mConnOverTime = time;
    }

    //设置当连接池忙时，获取连接的最大等待时间（秒）
    public void setGetWaitTime(int time)
    {
        this.mGetWaitTime = time;
    }

    //设置数据库类型
    public void setDbType(int type)
    {
        this.mDbType = type;
    }

    //获得数据库的类型
    public int getDbType()
    {
        return this.mDbType ;
    }

    public int getUseCount()
    {
        return this.mUseCount;
    }

    public int getPoolSize()
    {
        return this.mPoolSize;
    }

    public int getFreeCount()
    {
        return this.idleQueue.size();
    }

    //计算mUseCount
    public synchronized int calculateUseCount(int iTick)
    {
        mUseCount += iTick;
        return mUseCount;
    }

    //线程方法
    public void run()
    {

        LogUtil.WriteLog(Constant.NOTICE, "数据库连接池启动成功！");

        while ( bIsRun )
        {
            try
            {

                //打印连接使用情况
                long iCurrTime = System.currentTimeMillis() ;
                if ( (  iCurrTime - this.mLastPrintTime ) > 20*1000 )
                {
                    DbLog.writeLog("连接池使用情况: 使用数量=" + this.calculateUseCount(0) + ",空闲数量=" + this.idleQueue.size() );
                    this.mLastPrintTime = iCurrTime ;
                }

                //获取空闲连接，并对连接进行SQL探测和定期刷新
                this.execSqlTest();

                //检查连接池数量是否够，若不够创建新连接
                if ( (this.calculateUseCount(0) + this.idleQueue.size() ) < this.mPoolSize )
                {
                    this.appendNewConnection();
                }

                //等待
                Thread.sleep(100);

            }catch(Exception e)
            {
                DbLog.writeLog(e);
                try{Thread.sleep(2000);}catch(Exception ee){};
            }
        }
    }

    //为本连接池追加新连接
    protected void appendNewConnection() throws Exception
    {
        Connection dbConn = this.buildNewConnection();
        GessConnection dbGessConn = new GessConnection(this,dbConn);
        dbGessConn.setAutoCommit(false);
        this.idleQueue.add(dbGessConn);
        DbLog.writeLog("成功创建新连接" + dbGessConn.toString() + "." );
    }

    //执行SQL探测
    protected void execSqlTest() throws Exception
    {
        GessConnection dbConn = null;
        try
        {
            if ( this.idleQueue.size() > 0 )
            {
                dbConn = this.getConn(2);

                if ( dbConn.checkIsOvertime(this.mConnOverTime) ) //定期刷新
                {
                    DbLog.writeLog("连接" + dbConn.toString() + "经测定为过期，已抛弃！");
                    dbConn.close();
                    this.calculateUseCount(-1);//mUseCount--;
                }else if ( dbConn.checkIsNeedSqlTest(this.mSqlTestInvTime) ) //SQL探测
                {
                    boolean flag = dbConn.execSqlTest(this.mTestSql);
                    if ( flag == true )
                    {
                        DbLog.writeLog("连接" + dbConn.toString() + "经测定为正常！");
                        this.freeConn(dbConn);
                    }else
                    {
                        DbLog.writeLog("连接" + dbConn.toString() + "经测定为异常，已抛弃！");
                        dbConn.close();
                        this.calculateUseCount(-1);//mUseCount--;
                    }
                }else
                {
                    this.freeConn(dbConn);
                }
            }

        }catch(Exception e)
        {
            DbLog.writeLog(e);
        }
    }

    /**
     * 获取数据库连接连接
     * @param
     */
    public GessConnection getConn() throws Exception
    {
        return this.getConn(this.mGetWaitTime);
    }


    public GessConnection getConn(boolean autoCommit) throws Exception
    {
        GessConnection conn = this.getConn(this.mGetWaitTime);
        conn.setAutoCommit(autoCommit);
        return conn;
    }

    public GessConnection getConn(int iGetOvertime,boolean autoCommit) throws Exception
    {
        GessConnection conn = this.getConn(iGetOvertime);
        conn.setAutoCommit(autoCommit);
        return conn;
    }

    /**
     * 获取数据库连接连接
     * @param iGetOvertime  获取的超时时间（秒）
     */
    protected GessConnection getConn(int iGetOvertime) throws Exception
    {
        GessConnection dbConn = null;

        long iStartTime = System.currentTimeMillis();

        while ( true )
        {
            //获得空闲的连接
            dbConn = idleQueue.poll() ;
            if ( dbConn == null )
            {
                //如果未获得，判断当前连接池是否有有效的连接
                if ( ( this.calculateUseCount(0) + this.idleQueue.size() ) <= 0 )
                    throw new GessException(GessErrCode.HJ6003,"连接数据库失败，请稍候再试！");

                if ( ( System.currentTimeMillis() - iStartTime ) > iGetOvertime * 1000 )
                    throw new GessException(GessErrCode.HJ6004,"数据库忙，请稍候再试！");

                Thread.sleep(5);

            }else
            {

                this.calculateUseCount(1);//mUseCount++;

                //判断连接是否有效
                if ( dbConn.isClosed() == true)
                {
                    this.calculateUseCount(-1);//mUseCount--;
                    DbLog.writeLog("获取的新连接" + dbConn.toString() + "已关闭，重新获取!");
                    iStartTime = System.currentTimeMillis();
                    continue;
                }

                //判断连接是否已过期
                if ( dbConn.checkIsOvertime(this.mConnOverTime) == true )
                {
                    this.calculateUseCount(-1);//mUseCount--;
                    dbConn.close();
                    DbLog.writeLog("获取的新连接" + dbConn.toString() + "已过期，重新获取!");
                    iStartTime = System.currentTimeMillis();
                    continue;
                }

                //其它线程获取时打印获取时间
                if ( Thread.currentThread() != this )
                    dbConn.setGetTime();

                //DbLog.writeLog("等待时间[" + (System.currentTimeMillis()-iStartTime) + "]!");
                break;

            }
        }

        return dbConn;

    }

    /** 释放连接句柄 */
    protected void freeConn(GessConnection dbConn)
    {
        try
        {
            if ( dbConn == null )
                throw new Exception("严重错误：释放的连接为null!");

            if ( dbConn.getConnPool() != this )
                throw new Exception("严重错误：释放的连接非本连接池管理的连接！");

            //其它线程释放时打印释放时间
            if ( Thread.currentThread() != this )
                dbConn.setFreeTime();

            //判断是否已关闭
            if ( dbConn.isClosed() == true )
            {
                DbLog.writeLog("释放的连接" + dbConn.toString() + "为异常，已抛弃！");
            }else
            {
                //释放Statement
                dbConn.freeStatement();

                //添加到空闲队列
                idleQueue.add(dbConn);

            }

            //使用计数减
            this.calculateUseCount(-1);//mUseCount--;

        }catch(Exception e)
        {
            LogUtil.WriteLog(Constant.BADLY_ERROR, e);
        }

    }

    /**
     * 关闭连接池
     */
    public synchronized void close()
    {
        try
        {
            bIsRun = false;

            LogUtil.writeSystemLog(Constant.NOTICE, "开始关闭数据库连接...");
            for ( int i = 0 ; i < this.mPoolSize ; i++ )
            {
                Connection dbConn = this.getConn(20);
                dbConn.close();
                LogUtil.writeSystemLog(Constant.NOTICE, "已关闭[" + (i+1) + "].");
            }
            LogUtil.writeSystemLog(Constant.NOTICE, "数据库连接关闭完成");
        }catch(Exception e)
        {
            LogUtil.WriteLog(Constant.BADLY_ERROR, e);
        }
    }


    /**
     * 创建一个新的数据库连接
     */
    public Connection buildNewConnection() throws Exception
    {
        Class.forName(this.mDbDriver);
        DriverManager.setLoginTimeout(20);
        return DriverManager.getConnection(this.mDbUrl, this.mDbUid, this.mDbPwd);
    }

    public Connection buildNewConnection(boolean autoCommmit) throws Exception
    {
        Class.forName(this.mDbDriver);
        DriverManager.setLoginTimeout(20);
        Connection conn = DriverManager.getConnection(this.mDbUrl, this.mDbUid, this.mDbPwd);
        conn.setAutoCommit(autoCommmit);
        return conn;
    }

}